// 用于 TCP 打包与分包，在每条消息的头部加一个长度字段，一条完整的消息由 header + body 组成， body 就是消息内容。
// 每当 socket 可读时， muduo 的 TcpConnection 就会读取数据并存入 input buffer 然后回调用户函数，
// 调用用户回调函数时，此时 buffer 中很有可能不是完整的消息，于是加了中间层 codec ，
// 在收到一条完整的消息时才回调用户函数。
#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
#include <muduo/base/Logging.h>
#include <muduo/net/Buffer.h>
#include <muduo/net/Endian.h>
#include <muduo/net/TcpConnection.h>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>

class LengthHeaderCodec : boost::noncopyable {
public:
	typedef boost::function<void (const muduo::net::TcpConnectionPtr&, const muduo::string&, muduo::Timestamp)> StringMessageCallback;

	explicit LengthHeaderCodec(const StringMessageCallback &cb)
		: messageCallback_(cb)
	{}

	void OnMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp receiveTime) {
		// 数据会一直存放到应用层缓冲中，直到有完整的消息。
		while (buf->readableBytes() >= kHeaderLen) {
			const void *data = buf->peek();
			// 作者说这里有潜在的问题，由于这里直接访问 int32_t 很有可能不是内存对齐的地址，
			// 所以有些不支持的体系结构上运行，会导致 SIGBUS core dump 。
			// 正确做法应该调用 Buffer::peekInt32 。
			int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS
			const int32_t len = muduo::net::sockets::networkToHost32(be32);
			if (len > 65536 || len < 0) {
				LOG_INFO << "Invalid length " << len;
				conn->shutdown();
				break;
			} else if (buf->readableBytes() >= len + kHeaderLen) { // 如果是完整的消息，才进行处理
				buf->retrieve(kHeaderLen);
				muduo::string message(buf->peek(), len);
				messageCallback_(conn, message, receiveTime);
				buf->retrieve(len);
			} else {
				break;
			}
		}
	}

	void Send(muduo::net::TcpConnection *conn, const muduo::StringPiece &message) {
		muduo::net::Buffer buf;
		buf.append(message.data(), message.size());
		int32_t len = static_cast<int32_t>(message.size());
		int32_t be32 = muduo::net::sockets::hostToNetwork32(len);
		buf.prepend(&be32, sizeof(be32));
		conn->send(&buf);
	}

private:
	const static size_t kHeaderLen = sizeof(int32_t); // 消息 header 的字节数

	StringMessageCallback messageCallback_;
};

#endif // MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
